
#include "XmlDocument.h"

XmlDocument::XmlDocument() : statement(L"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"){
	rootElem = NULL;
}

XmlElement* XmlDocument::GetRootElem(){
	return rootElem;
}

ERROR_CODE XmlDocument::ReadIn(wstring strFilePath){
	ifstream inFile(strFilePath.c_str());
	inFile.seekg(0, ios::end);
	int iLen = inFile.tellg();
	char *buf = new char[iLen + 1];
	inFile.seekg(0, ios::beg);
	inFile.read(buf, iLen);
	buf[iLen] = 0;
	int iwLen = ::MultiByteToWideChar(CP_ACP, 0, buf, -1, NULL, 0);
	wchar_t *wbuf = new wchar_t[iwLen];
	::MultiByteToWideChar(CP_ACP, 0, buf, iLen, wbuf, iwLen);
	delete[] buf;
	inFile.close();
	return Parse(wbuf, iwLen);
}

// Parse the document and generate a DOM tree
ERROR_CODE XmlDocument::Parse(wchar_t* wbuf, int iwLen){
	int iIndex = 0;
	stack<XmlElement*> stElems;
	// Ignore the statement of in the head part
	if(hasStatement(wbuf, iwLen, iIndex)){
		// Allow "\n" and " " endless
		while(wbuf[iIndex] == wNewLine || wbuf[iIndex] == wWhiteSp || wbuf[iIndex] == wTab){
			iIndex ++;
			if(iIndex == iwLen){
				return FORMAT_ERROR;
			}
		}

		ATTRIBUTES attr;
		wstring strName;

		while (iIndex < iwLen)
		{
			//pre: if no end tag like "<% />"
			//pre: element and tag cann't use the same name
			//post: strName is the name of the tag
			//post: attr is the map of the attributes
			attr.clear();
			strName.clear();
			ERROR_CODE ret = ParseNode(wbuf, iwLen, iIndex, strName, attr);
			if(ret == ISNODE){
				// This tag is a truely node
				if(stElems.top() == NULL)
					return NO_PARENT;
				XmlNode *pNode = new XmlNode();
				pNode->SetName(strName);
				pNode->SetAttr(attr);
				stElems.top()->AddElement(pNode);
			}

			else if(ret == ISCLOSETAG){
				// This tag is a closed tag
				XmlElement *pElem = stElems.top();
				if(pElem->GetName() != strName){
					return NO_MATCH_TAG;
				}
				// Then pop the top Element
				stElems.pop();
				// If only one element is left
				if(stElems.size() == 1)
					return SUCCEED;
			}
			else if(ret == ISELEMENT){
				XmlElement *cur = new XmlElement();
				cur->SetName(strName);
				cur->SetAttr(attr);	


				if(stElems.size() == 0){
					// The first time push a NULL in, then you can use it to decide if the last one has been popped out.
					stElems.push(NULL);
					stElems.push(cur);
					rootElem = cur;
				}
				else{
					// Any Elements has a father Element
					if(stElems.top() == NULL) return NO_PARENT; 

					// The child elements are added to her father
					stElems.top()->AddElement(cur);

					// The top of the stack changed
					stElems.push(cur);
				}
			}
			else{
				return FORMAT_ERROR;
			}
		}
	}
	else {
		return FORMAT_ERROR;
	}
	// Only one null should be left
	if(stElems.size() != 1)
		return NO_MATCH_TAG;
	return SUCCEED;
}

// The name of the node and the Attribute of It will be returned, Even if it's not a node. 
ERROR_CODE XmlDocument::ParseNode(wchar_t* wbuf, int iwLen, int& iIndex, wstring& name, ATTRIBUTES& attr){
	// Ignore any " " or "\n" marks
	while (wbuf[iIndex] == wWhiteSp || wbuf[iIndex] == wNewLine || wbuf[iIndex] == wTab) {
		++ iIndex;
		if(iIndex == iwLen){
			return FORMAT_ERROR;
		}
	}
	// The first tag should be a left bracket
	if(wbuf[iIndex] != wLeftBra){
		return FORMAT_ERROR;
	}
	++ iIndex;

	//TEST CASE: "<  File />" or </File>

	// Ignore any white space
	while(wbuf[iIndex] == wWhiteSp || wbuf[iIndex] == wTab) {
		++ iIndex;
		if(iIndex == iwLen){
			return FORMAT_ERROR;
		}
	}

	bool closeTagFlag = false;
	if(wbuf[iIndex] == wSlash){
		closeTagFlag = true;
		++iIndex;
	}
	// Get the tag's name
	int i = 0;
	while(wbuf[iIndex] != wTab && wbuf[iIndex] != wWhiteSp && wbuf[iIndex] != wRightBra && wbuf[iIndex] != wSlash){
		chTemp[i ++] = wbuf[iIndex++];
		if(iIndex == iwLen){
			return FORMAT_ERROR;
		}
	}
	chTemp[i] = 0;
	name = chTemp;
	// If it's a close tag, then return true
	if(closeTagFlag){
		++ iIndex;
		return ISCLOSETAG;
	}
	//Ignore any " "
	while(wbuf[iIndex] == wWhiteSp || wbuf[iIndex] == wTab){
		++ iIndex;
		if(iIndex == iwLen){
			return FORMAT_ERROR;
		}
	}

	//TEST CASE: if the next is "/>" then just jump out of the "while" else it has attributes
	// When came here, there is no white space
	while(wbuf[iIndex] != wSlash && wbuf[iIndex] != wRightBra){
		wstring strAttrName;
		i = 0;

		// CASE: "<.... Name  =   "XXXX"  "
		while(wbuf[iIndex] != wEqual && wbuf[iIndex] != wWhiteSp && wbuf[iIndex] != wTab){
			chTemp[i ++] = wbuf[iIndex ++];
			if(iIndex == iwLen){
				return FORMAT_ERROR;
			}
		}
		while (wbuf[iIndex] == wWhiteSp || wbuf[iIndex] == wTab) {
			++ iIndex;
			if(iIndex == iwLen){
				return FORMAT_ERROR;
			}
		}
		// Ignore the equal mark
		++ iIndex;
		while (wbuf[iIndex] == wWhiteSp || wbuf[iIndex] == wTab){
			++ iIndex;
			if(iIndex == iwLen){
				return FORMAT_ERROR;
			}
		}
		// Ignore the quotation mark
		++ iIndex;
		chTemp[i] = 0;
		strAttrName = chTemp;
		wstring strAttrValue;
		i = 0;
		while(wbuf[iIndex] != wQuote){
			chTemp[i ++] = wbuf[iIndex ++];
			if(iIndex == iwLen){
				return FORMAT_ERROR;
			}
		}
		// Ignore the quotation mark
		++ iIndex;
		chTemp[i] = 0;
		strAttrValue = chTemp;
		attr[strAttrName] = strAttrValue;
		// Ignore all white space
		while(wbuf[iIndex] == wWhiteSp || wbuf[iIndex] == wTab) {
			++ iIndex; 
			if(iIndex == iwLen){
				return FORMAT_ERROR;
			}
		}
	}

	// If stopped by the ">"
	if(wbuf[iIndex] == wRightBra){
		++ iIndex;
		return ISELEMENT;
	}
	// Stopped by the "/>"
	else{
		// Ignore the "/" and the ">"
		++ iIndex;
		++ iIndex;
		return ISNODE;
	}

}


bool XmlDocument::hasStatement(wchar_t* wbuf, int iwLen, int &iIndex){
	stack<wchar_t> stStatement;
	int i = 0;
	// Ignore the preceding whitespace 
	while(wbuf[iIndex] == wWhiteSp || wbuf[iIndex] == wTab){
		++ iIndex;
		if(iIndex == iwLen){
			return false;
		}
	}
	// If the left bracket meeted
	// The whole statement is in the strStatement inlcuding "<? ?>"
	if(wbuf[iIndex] == wLeftBra){
		chTemp[i++] = wbuf[iIndex];
		++ iIndex;
		//The next must be '?'
		if(wbuf[iIndex] != wAsk){
			return false;
		}
		chTemp[i++] = wbuf[iIndex];
		++ iIndex;
		while(wbuf[iIndex] != wAsk && iIndex < iwLen){
			chTemp[i ++] = wbuf[iIndex];
			++ iIndex;
			if(iIndex == iwLen){
				return false;
			}
		}
		if(iIndex < iwLen){
			chTemp[i ++] = wbuf[iIndex];
			++ iIndex;
			if(wbuf[iIndex] != wRightBra)
				return false;
			chTemp[i ++] = wbuf[iIndex];
			chTemp[i] = 0;
			++ iIndex;
			statement = chTemp;
			return true;
		}
		else {
			return false;
		}
	}
	else{
		return false;
	}
	return false;
}



void XmlDocument::OutPut(wstring strFilePath){
	wofstream newFile(strFilePath.c_str());
	newFile.imbue(locale("chs"));
	newFile << statement << endl;
	int iIndent = 0;
	rootElem->Output(newFile, iIndent);
	newFile.close();
}

XmlDocument::~XmlDocument(void)
{
	if(rootElem)
		delete rootElem;
	rootElem = NULL;
}